package com.bianpai.log;

import com.bianpai.core.Flow;
import com.bianpai.core.FlowContext;
import com.bianpai.core.Node;
import com.bianpai.core.NodeResult;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;

/**
 * 数据库流程日志记录器，将日志记录到数据库中
 */
public class DatabaseFlowLogger implements FlowLogger {
    private DataSource dataSource;
    
    public DatabaseFlowLogger(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public void logFlowStart(Flow flow, FlowContext context) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, event_type, event_time, message) VALUES (?, ?, ?, ?, ?)";
        executeUpdate(sql, flow.getId(), context.getFlowInstanceId(), "FLOW_START", LocalDateTime.now(), 
                "Flow " + flow.getName() + " started");
    }
    
    @Override
    public void logFlowComplete(Flow flow, FlowContext context, boolean success, String message) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, event_type, event_time, success, message) VALUES (?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, flow.getId(), context.getFlowInstanceId(), "FLOW_COMPLETE", LocalDateTime.now(), 
                success ? 1 : 0, message != null ? message : (success ? "Flow completed successfully" : "Flow failed"));
    }
    
    @Override
    public void logNodeStart(Node node, FlowContext context) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, message) VALUES (?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_START", 
                LocalDateTime.now(), "Node " + node.getName() + " started");
    }
    
    @Override
    public void logNodeComplete(Node node, FlowContext context, NodeResult result) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, success, message) VALUES (?, ?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_COMPLETE", 
                LocalDateTime.now(), result.isSuccess() ? 1 : 0, result.getMessage());
    }
    
    @Override
    public void logNodeError(Node node, FlowContext context, Exception exception) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, success, message, error_details) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_ERROR", 
                LocalDateTime.now(), 0, "Node execution error", exception.getMessage());
    }
    
    @Override
    public void logRollbackStart(Flow flow, FlowContext context) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, event_type, event_time, message) VALUES (?, ?, ?, ?, ?)";
        executeUpdate(sql, flow.getId(), context.getFlowInstanceId(), "ROLLBACK_START", LocalDateTime.now(), 
                "Rollback started for flow " + flow.getName());
    }
    
    @Override
    public void logRollbackComplete(Flow flow, FlowContext context) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, event_type, event_time, message) VALUES (?, ?, ?, ?, ?)";
        executeUpdate(sql, flow.getId(), context.getFlowInstanceId(), "ROLLBACK_COMPLETE", LocalDateTime.now(), 
                "Rollback completed for flow " + flow.getName());
    }
    
    @Override
    public void logNodeRollbackStart(Node node, FlowContext context) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, message) VALUES (?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_ROLLBACK_START", 
                LocalDateTime.now(), "Rollback started for node " + node.getName());
    }
    
    @Override
    public void logNodeRollbackComplete(Node node, FlowContext context, boolean success) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, success, message) VALUES (?, ?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_ROLLBACK_COMPLETE", 
                LocalDateTime.now(), success ? 1 : 0, success ? "Node rollback successful" : "Node rollback failed");
    }
    
    @Override
    public void logNodeRollbackError(Node node, FlowContext context, Exception exception) {
        String sql = "INSERT INTO flow_log (flow_id, flow_instance_id, node_id, event_type, event_time, success, message, error_details) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
        executeUpdate(sql, context.getFlowId(), context.getFlowInstanceId(), node.getId(), "NODE_ROLLBACK_ERROR", 
                LocalDateTime.now(), 0, "Node rollback error", exception.getMessage());
    }
    
    /**
     * 执行SQL更新操作
     * @param sql SQL语句
     * @param params 参数
     */
    private void executeUpdate(String sql, Object... params) {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                if (param instanceof LocalDateTime) {
                    stmt.setTimestamp(i + 1, Timestamp.valueOf((LocalDateTime) param));
                } else {
                    stmt.setObject(i + 1, param);
                }
            }
            
            stmt.executeUpdate();
        } catch (SQLException e) {
            // 记录日志失败不应该影响业务流程，所以这里只打印错误
            e.printStackTrace();
        }
    }
    
    /**
     * 创建数据库表
     * 这个方法应该在应用启动时调用一次
     */
    public void createTableIfNotExists() {
        String sql = "CREATE TABLE IF NOT EXISTS flow_log (" +
                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
                "flow_id VARCHAR(100) NOT NULL, " +
                "flow_instance_id VARCHAR(100) NOT NULL, " +
                "node_id VARCHAR(100), " +
                "event_type VARCHAR(50) NOT NULL, " +
                "event_time TIMESTAMP NOT NULL, " +
                "success TINYINT, " +
                "message TEXT, " +
                "error_details TEXT, " +
                "INDEX idx_flow_instance (flow_instance_id), " +
                "INDEX idx_event_time (event_time)" +
                ")";
        
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}